package com.sql.mysql.sharding.reflection;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.reflections.Reflections;
import org.reflections.scanners.MethodAnnotationsScanner;

import com.sql.mysql.sharding.annotation.ParameterIndex;
import com.sql.mysql.sharding.annotation.SqlField;
import com.sql.mysql.sharding.annotation.ShardingKeyObject;
import com.sql.mysql.sharding.annotation.ShardingKeyType;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ShardingReflectionHelper {

	public static ConcurrentHashMap<Method, ShardingKeyObject> map = new ConcurrentHashMap<Method, ShardingKeyObject>();

	//
	public static ShardingKeyObject getCopiedShardingKeyObject(Method method) {
		ShardingKeyObject found = map.get(method);
		if (null == found) {
			return null;
		}
		// 开始拷贝吧
		ShardingKeyObject copiedShardingKeyObject = new ShardingKeyObject();
		copiedShardingKeyObject.setMethod(found.getMethod());
		copiedShardingKeyObject.setAnnotationType(found.getAnnotationType());
		copiedShardingKeyObject.setFiedName(found.getFiedName());
		copiedShardingKeyObject.setParameterIndex(found.getParameterIndex());
		copiedShardingKeyObject.setParameterMapping(null);
		copiedShardingKeyObject.setJdbcType(null);
		copiedShardingKeyObject.setValue(null);
		return copiedShardingKeyObject;
	}

	public static void scan() throws Exception {
		//
		log.debug("begin to scan all interface files");
		// 增加过滤器
		Reflections reflections = new Reflections(new MethodAnnotationsScanner(), new MyPredicate());
		//
		Set<Method> totalMethods = new HashSet<Method>();
		totalMethods.addAll(reflections.getMethodsAnnotatedWith(SqlField.class));
		totalMethods.addAll(reflections.getMethodsAnnotatedWith(ParameterIndex.class));
		log.debug("totalMethods {}", totalMethods);
		// 解析每个方法,结果存到map里
		for (Method method : totalMethods) {
			log.debug("----------------------------------------");
			// 先拿到类.方法名
			// StringBuilder builder = new StringBuilder();
			// builder.append(method.getDeclaringClass().getName());
			// builder.append(".");
			// builder.append(method.getName());
			// String classMethodName = builder.toString();
			// log.debug("classMethodName: {}", classMethodName);
			// 再拿到参数
			// Class<?>[] parameterTypesClass = method.getParameterTypes();
			// log.debug("parameterTypesClass: {}", parameterTypesClass);
			// 生成对象
			// ShardingMethodSignature key = new ShardingMethodSignature();
			// key.setClassMethodName(classMethodName);
			// key.setParameterTypesClass(parameterTypesClass);
			//
			ShardingKeyObject value = parseShardingKey(method);
			// 保存
			if (null != map.put(method, value)) {
				log.error("more method {} defined,not allowed", method);
				System.exit(-1);// 直接退出
			}
			log.debug("----------------------------------------");
		}
	}

	// 通过方法拿注解的ShardingKey
	public static ShardingKeyObject parseShardingKey(Method method) throws Exception {
		ShardingKeyObject shardingKeyObject = new ShardingKeyObject();
		shardingKeyObject.setMethod(method);
		//
		SqlField sqlKey = method.getAnnotation(SqlField.class);
		// 1)如果是方法上注解
		if (null != sqlKey) {
			String name = sqlKey.value();
			if (null != name) {
				name = name.trim();
				if (name.length() != 0) {
					shardingKeyObject.setAnnotationType(ShardingKeyType.SQL_FIELD);
					shardingKeyObject.setFiedName(name);
				}
			}
		} else {// 2)参数的序号,0开始
			ParameterIndex paramKey = method.getAnnotation(ParameterIndex.class);
			if (null != paramKey) {
				int value = paramKey.value();
				if (value >= 0) {
					shardingKeyObject.setParameterIndex(value);
					shardingKeyObject.setAnnotationType(ShardingKeyType.PARAMETER_INDEX);
				}
			}
		}
		// 3)如果都没设置,就报错---其实也就是说,所有的sql都必须有注解,暂时不容许广播形式
		// 登录怎么办?
		if (ShardingKeyType.OTHER == shardingKeyObject.getAnnotationType()) {
			log.error("no valid sharding key defined for {}", method);
			throw new Exception("no shardingkey defined for " + method);
		}
		// 4)返回
		return shardingKeyObject;
	}

	// public static void main(String[] args) throws Exception {
	// scan();
	// }
}
